using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum StatType
{
    Strength,
    Agility,
    Intelligence,
    Vitality,
    Damage,
    CritChance,
    CritPower,
    Health,
    Armor,
    Evasion,
    MagicResistance,
    FireDamage,
    IceDamage,
    LightingDamage,
}

public class CharacterStats : MonoBehaviour
{
    public event Action<int, int> OnHpChanged;
    protected EntityFX Fx { get; private set; }
    [Header("Major stats")]
    // 主要属性
    // 力量，1 point increases damage by 1 and crit.power by 1%
    public Stat strength;
    // 敏捷，1 point increases evasion rate by 1% and crit.chance by 1%
    public Stat agility;
    // 智力, 1 point increases magic damage by 1 and magic resistance by 3
    public Stat intelligence;
    // 耐力，1 point increases maxHp by 3 or 5 points
    public Stat vitality;
    [Header("Offensive  stats")]
    // 损伤
    public Stat damage;
    public Stat critChance;
    // default value 150%
    public Stat critPower;
    [Header("Defensive stats")]
    // 最大生命值
    public Stat maxHp;
    // 护甲
    public Stat armor;
    // 闪避
    public Stat evasion;
    // 魔法抗性
    public Stat magicResistance;
    [Header("Magic stats")]
    public Stat fireDamage;
    public Stat iceDamage;
    public Stat lightningDamage;
    [Space]
    // 是否被点燃 does damage over time
    private bool isIgnited;
    // 是否被冰冻 reduces armor by 20%
    private bool isChilled;
    // 是否被电击 reduces accuracy by 20%
    private bool isShocked;
    [Space]
    [SerializeField] private float ailmentsDuration = 4f;
    protected float ignitedTimer;
    protected float chilledTimer;
    protected float shockedTimer;
    private GameObject shockStrikePrefab;
    [Space]
    protected float ignitedDamageCooldown = 0.3f;
    protected float ignitedDamageTimer;
    protected int igniteDamage;
    protected int shockDamage;
    // 当前生命值
    public int CurrentHp { get; private set; }
    public bool IsDead { get; private set; }
    public bool IsInvincible { get; private set; }
    protected bool isVulnerable;

    protected virtual void Awake()
    {
        CurrentHp = GetMaxHp();
        critPower.SetDefaultValue(150);
        Fx = GetComponent<EntityFX>();
        shockStrikePrefab = Resources.Load<GameObject>("Prefabs/Skills/shock_strike_prefab");
    }

    protected virtual void Update()
    {
        ignitedTimer -= Time.deltaTime;
        chilledTimer -= Time.deltaTime;
        shockedTimer -= Time.deltaTime;
        ignitedDamageTimer -= Time.deltaTime;
        if (ignitedTimer <= 0 && isIgnited)
            isIgnited = false;
        if (chilledTimer <= 0 && isChilled)
            isChilled = false;
        if (shockedTimer <= 0 && isShocked)
            isShocked = false;
        if (isIgnited)
            ApplyIgniteDamage();
    }

    public virtual void TakeDamage(int damage)
    {
        if (IsInvincible) return;
        DecreaseHpBy(damage);
        if (CurrentHp <= 0 && !IsDead)
            Die();
        GetComponent<Entity>().DamageImpact();
        _ = Fx.StartCoroutine("FlashFX");
    }

    public virtual void IncreaseHpBy(int healAmount)
    {
        if (CurrentHp == GetMaxHp() || healAmount <= 0 || IsDead)
            return;
        CurrentHp = Mathf.Clamp(CurrentHp + healAmount, 0, GetMaxHp());
        OnHpChanged?.Invoke(GetMaxHp(), CurrentHp);
    }

    protected virtual void DecreaseHpBy(int damage, UnityEngine.Color color = default)
    {
        if (IsDead) return;
        if (CurrentHp <= 0)
            return;
        if (isVulnerable)
            damage = Mathf.RoundToInt(damage * 1.5f);
        CurrentHp = Mathf.Clamp(CurrentHp - damage, 0, GetMaxHp());
        if (damage > 0)
        {
            Fx.CreatePopUp(damage.ToString(), color);
        }
        OnHpChanged?.Invoke(GetMaxHp(), CurrentHp);
    }

    public void MakeVulnerableFor(float duration)
    {
        if (isVulnerable) return;
        isVulnerable = true;
        _ = StartCoroutine(VulnerableCoroutine(duration));
    }

    private IEnumerator VulnerableCoroutine(float duration)
    {
        isVulnerable = true;
        yield return new WaitForSeconds(duration);
        isVulnerable = false;
    }

    public virtual void IncreaseStatBy(int modifier, float duration, Stat statModifier)
    {
        /* start coroutine for stat increase */
        _ = StartCoroutine(StatModifier(modifier, duration, statModifier));
    }

    private IEnumerator StatModifier(int modifier, float duration, Stat statModifier)
    {
        statModifier.AddModifier(modifier);
        yield return new WaitForSeconds(duration);
        statModifier.RemoveModifier(modifier);
    }

    public virtual void DoDamage(CharacterStats targetStats)
    {
        if (targetStats.IsInvincible)
        {
            return;
        }
        if (TargetCanAvoidAttack(targetStats))
        {
            return;
        }
        targetStats.GetComponent<Entity>().SetupKnockbackDirection(transform.position);
        int totalDamage = damage.GetValue() + strength.GetValue();
        bool isCritical = false;
        if (CanCrit())
        {
            totalDamage = CalculateCriticalDamage(totalDamage);
            isCritical = true;
        }
        Fx.CreateHitFX(targetStats.transform, isCritical);
        totalDamage = CheckTargetArmor(targetStats, totalDamage);
        targetStats.TakeDamage(totalDamage);
        /* if inventory current weapon is magical, then apply magical damage
        or remove if you don't want to apply magic hit on primary attack */
        DoMagicalDamage(targetStats);
    }

    #region Magical damage and ailments
    public virtual void DoMagicalDamage(CharacterStats targetStats)
    {
        int fireDamage = this.fireDamage.GetValue();
        int iceDamage = this.iceDamage.GetValue();
        int lightningDamage = this.lightningDamage.GetValue();
        int totalMagicDamage = fireDamage + iceDamage + lightningDamage + intelligence.GetValue();
        totalMagicDamage = CheckTargetResistance(targetStats, totalMagicDamage);
        targetStats.TakeDamage(totalMagicDamage);
        if (Mathf.Max(fireDamage, iceDamage, lightningDamage) <= 0)
            return;
        AttemptToApplyAilments(targetStats, fireDamage, iceDamage, lightningDamage);
    }

    private void AttemptToApplyAilments(CharacterStats targetStats, int fireDamage, int iceDamage, int lightingDamage)
    {
        bool canApplyIgnite = fireDamage > iceDamage && fireDamage > lightingDamage;
        bool canApplyChill = iceDamage > fireDamage && iceDamage > lightingDamage;
        bool canApplyShock = lightingDamage > fireDamage && lightingDamage > iceDamage;
        if (!canApplyChill && !canApplyIgnite && !canApplyShock)
        {
            List<int> ailmentList = new();
            if (fireDamage > 0)
                ailmentList.Add(0);
            if (iceDamage > 0)
                ailmentList.Add(1);
            if (lightingDamage > 0)
                ailmentList.Add(2);
            int r = UnityEngine.Random.Range(0, ailmentList.Count);
            //canApplyIgnite = canApplyChill = canApplyShock = false;
            switch (ailmentList[r])
            {
                case 0:
                    canApplyChill = canApplyShock = false;
                    canApplyIgnite = true;
                    targetStats.ApplyAilments(canApplyIgnite, canApplyChill, canApplyShock);
                    break;
                case 1:
                    canApplyIgnite = canApplyShock = false;
                    canApplyChill = true;
                    targetStats.ApplyAilments(canApplyIgnite, canApplyChill, canApplyShock);
                    break;
                case 2:
                    canApplyIgnite = canApplyChill = false;
                    canApplyShock = true;
                    targetStats.ApplyAilments(canApplyIgnite, canApplyChill, canApplyShock);
                    break;
            }
        }
        if (canApplyIgnite)
            targetStats.SetupIgniteDamage(Mathf.RoundToInt(fireDamage * 0.15f));
        if (canApplyShock)
            targetStats.SetupShockDamage(Mathf.RoundToInt(lightingDamage * 0.1f));
        targetStats.ApplyAilments(canApplyIgnite, canApplyChill, canApplyShock);
    }

    /// <summary>
    /// 施加异常状态
    /// </summary>
    /// <param name="ignite">点燃</param>
    /// <param name="chill">冰冻</param>
    /// <param name="shock">电击</param>
    public virtual void ApplyAilments(bool ignite, bool chill, bool shock)
    {
        bool canApplyIgnite = !isIgnited && !isChilled && !isShocked;
        bool canApplyChill = !isIgnited && !isChilled && !isShocked;
        bool canApplyShock = !isIgnited && !isChilled;
        if (ignite && canApplyIgnite)
        {
            isIgnited = true;
            ignitedTimer = ailmentsDuration;
            Fx.IgniteFxFor(ailmentsDuration);
        }

        if (chill && canApplyChill)
        {
            isChilled = true;
            chilledTimer = ailmentsDuration;
            float slowPercentage = 0.3f;
            GetComponent<Entity>().SlowEntityBy(slowPercentage, ailmentsDuration);
            Fx.ChillFxFor(ailmentsDuration);
        }

        if (shock && canApplyShock)
        {
            if (!isShocked)
                ApplyShock(shock);
            else
            {
                if (TryGetComponent(out Player _)) return;
                HitNearestTargetWithShockStrike();
            }
        }
    }

    public void ApplyShock(bool shock)
    {
        if (isShocked) return;
        isShocked = shock;
        shockedTimer = ailmentsDuration;
        Fx.ShockFxFor(ailmentsDuration);
    }

    private void HitNearestTargetWithShockStrike()
    {
        // find the closest enemy and apply shock
        // Instantiate thunderstrike prefab
        // setup damage and target
        // Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, 10);
        Collider2D[] results = new Collider2D[20];
        var size = Physics2D.OverlapCircleNonAlloc(transform.position, 10, results);
        float closestDistance = Mathf.Infinity;
        Transform closestEnemyTf = null;
        for (int i = 0; i < size; i++)
        {
            if (results[i].TryGetComponent(out Enemy closestEnemy) &&
                Vector2.Distance(transform.position, closestEnemy.transform.position) > 1)
            {
                float distance = Vector2.Distance(transform.position, closestEnemy.transform.position);
                if (distance < closestDistance)
                {
                    closestDistance = distance;
                    closestEnemyTf = closestEnemy.transform;
                }
            }
            // delete, if you don't want shocked target to be hit by shock strike
            if (closestEnemyTf == null)
                closestEnemyTf = transform;
        }
        // foreach (Collider2D collider in colliders)
        // {
        //     if (collider.TryGetComponent(out Enemy closestEnemy) &&
        //         Vector2.Distance(transform.position, collider.transform.position) > 1)
        //     {
        //         float distance = Vector2.Distance(transform.position, collider.transform.position);
        //         if (distance < closestDistance)
        //         {
        //             closestDistance = distance;
        //             closestEnemyTf = collider.transform;
        //         }
        //     }
        //
        //     // delete, if you don't want shocked target to be hit by shock strike
        //     if (closestEnemyTf == null)
        //     {
        //         closestEnemyTf = transform;
        //     }
        // }

        if (!closestEnemyTf) return;
        GameObject newShockStrike = Instantiate(shockStrikePrefab, transform.position, Quaternion.identity);
        newShockStrike.GetComponent<ShockStrikeController>()
            .Setup(shockDamage, closestEnemyTf.GetComponent<CharacterStats>());
    }

    private void ApplyIgniteDamage()
    {
        if (ignitedDamageTimer > 0) return;
        DecreaseHpBy(igniteDamage, UnityEngine.Color.yellow);
        if (CurrentHp <= 0 && !IsDead)
            Die();
        ignitedDamageTimer = ignitedDamageCooldown;
    }

    public void SetupIgniteDamage(int damage) => igniteDamage = damage;

    public void SetupShockDamage(int damage) => shockDamage = damage;
    #endregion

    #region Stat calulations
    protected int CheckTargetArmor(CharacterStats targetStats, int totalDamage)
    {
        if (targetStats.isChilled)
            totalDamage -= Mathf.RoundToInt(targetStats.armor.GetValue() * 0.8f);
        else
            totalDamage -= targetStats.armor.GetValue();
        totalDamage = Mathf.Clamp(totalDamage, 1, int.MaxValue);
        return totalDamage;
    }

    private int CheckTargetResistance(CharacterStats targetStats, int totalMagicDamage)
    {
        totalMagicDamage -= targetStats.magicResistance.GetValue() + targetStats.intelligence.GetValue() * 3;
        totalMagicDamage = Mathf.Clamp(totalMagicDamage, 1, int.MaxValue);
        return totalMagicDamage;
    }

    public virtual void OnEvasion() { }

    /// <summary>
    /// 目标可以躲避攻击
    /// </summary>
    /// <param name="targetStats"></param>
    /// <returns></returns>
    protected bool TargetCanAvoidAttack(CharacterStats targetStats)
    {
        int totalEvasion = targetStats.evasion.GetValue() + targetStats.armor.GetValue() +
                           targetStats.agility.GetValue();
        if (isShocked) totalEvasion += 20;
        bool result = UnityEngine.Random.Range(0, 100) < totalEvasion;
        if (result)
            targetStats.OnEvasion();
        return result;
    }

    protected bool CanCrit()
    {
        int totalCriticalChance = critChance.GetValue() + agility.GetValue();
        return UnityEngine.Random.Range(0, 100) <= totalCriticalChance;
    }

    protected int CalculateCriticalDamage(int damage)
    {
        float totalCritPower = (critPower.GetValue() + strength.GetValue()) * 0.01f;
        float critDamage = damage * totalCritPower;
        return Mathf.RoundToInt(critDamage);
    }

    public int GetMaxHp() => maxHp.GetValue() + vitality.GetValue() * 5;
    #endregion

    protected virtual void Die()
    {
        if (IsDead) return;
        IsDead = true;
    }

    public void KillEntity() => Die();

    public void MakeInvincible(bool IsInvincible)
    {
        this.IsInvincible = IsInvincible;
    }

    public Stat GetStat(StatType statType)
    {
        return statType switch
        {
            StatType.Strength => strength,
            StatType.Agility => agility,
            StatType.Intelligence => intelligence,
            StatType.Vitality => vitality,
            StatType.Damage => damage,
            StatType.CritChance => critChance,
            StatType.CritPower => critPower,
            StatType.Health => maxHp,
            StatType.Armor => armor,
            StatType.Evasion => evasion,
            StatType.MagicResistance => magicResistance,
            StatType.FireDamage => fireDamage,
            StatType.IceDamage => iceDamage,
            StatType.LightingDamage => lightningDamage,
            _ => null,
        };
    }
}